#!/usr/bin/env python

from pathlib import Path
import time, threading
import gi

gi.require_versions(dict(Gtk="4.0", Adw="1"))
from gi.repository import GObject, GLib, Gio, Adw, Gtk, Gdk
from tagged.template import Template
import typing


class FileItem(GObject.GObject):
    __gtype_name__ = "FileItem"

    base = GObject.Property(type=str)
    path = GObject.Property(type=str)
    size = GObject.Property(type=int)
    ctime = GObject.Property(type=int)

    def __init__(self, base: Path, p: Path):
        super().__init__()

        self.base = str(base.parent)
        self.path = str(base.name / p.relative_to(base))
        stat = p.stat()
        self.size = stat.st_size
        self.ctime = stat.st_ctime

    @GObject.Property(type=str)
    def name(self):
        return Path(self.path).name

    @GObject.Property(type=str)
    def full_path(self):
        return str(Path(self.base) / self.path)


@Template.from_file("./ui/select-folder-dialog.ui")
class SelectFolderDialog(Gtk.Dialog):
    __gtype_name__ = "SelectFolderDialog"

    sep = "\n"

    folder = GObject.Property(type=str)
    filter = GObject.Property(type=str)

    def __init__(self, parent: Gtk.Window, callback: typing.Callable):
        super().__init__(
            application=parent.get_application(),
            transient_for=parent,
            title="Select Folder",
            use_header_bar=True,
            destroy_with_parent=True,
            hide_on_close=False,
            modal=True,
        )
        self.callback = callback
        self.set_visible(True)

    @Template.Callback()
    def on_reponse(self, _, resp: Gtk.ResponseType):
        if self.callback is not None:
            self.callback(self, resp)

        self.destroy()

    @Template.Callback()
    def on_folder_clicked(self, *args):
        file_dialog = Gtk.FileDialog()

        def on_file_chosen(src, result):
            try:
                files = file_dialog.select_multiple_folders_finish(result)
                if files is None:
                    return
                self.folder = self.sep.join([f.get_path() for f in files])
            except:
                pass

        file_dialog.select_multiple_folders(self, callback=on_file_chosen)

    def get_selected_paths(self):
        ret = []

        folders = self.folder.split(self.sep)
        for folder_str in folders:
            folder = Path(folder_str)
            filter = self.filter or "*"
            paths = folder.glob(filter)
            ret.extend([FileItem(folder, p) for p in paths])

        return sorted(ret, key=lambda it: it.name)


@Template.from_file("./ui/progress-dialog.ui")
class ProgressDialog(Adw.Window):
    __gtype_name__ = "ProgressDialog"

    progress: Gtk.ProgressBar = Template.Child()

    current_file = GObject.Property(type=str)

    def __init__(self, parent: Gtk.Window):
        super().__init__(
            application=parent.get_application(),
            transient_for=parent,
            deletable=False,
            destroy_with_parent=True,
            hide_on_close=False,
            modal=True,
        )

    @Template.Callback()
    def get_tips(self, target, current_file, progress):
        if current_file == "":
            if progress == 0:
                return "Ready"
            else:
                return "Done"
        return "Copying {}...".format(current_file)

    def set_progress(self, fraction, current_file):
        self.current_file = current_file
        self.progress.set_fraction(fraction)

    @Template.Callback()
    def is_done(self, target, fraction):
        return fraction == 1

    @Template.Callback()
    def dismiss(self, *args):
        self.destroy()


@Template.from_file("./ui/main-window.ui")
class MainWindow(Adw.ApplicationWindow):
    __gtype_name__ = "MainWindow"

    targetFolder = GObject.Property(type=str)

    file_list: Gio.ListStore = Template.Child()
    file_sorted_list: Gtk.SortListModel = Template.Child()

    add_btn = Template.Child()
    # del_btn = Template.Child()

    def __init__(self, application: Adw.Application):
        super().__init__(application=application, title="Sequential Copy")

        drop_target = Gtk.DropTarget.new(str, Gdk.DragAction.COPY)
        drop_target.connect("drop", self.on_drop)
        self.add_controller(drop_target)

    @Template.Callback()
    def is_executable(self, target, text):
        return text != ""

    @Template.Callback()
    def get_file_name(self, info: FileItem):
        if info is None:
            return ""
        return info.name

    @Template.Callback()
    def get_file_path(self, info: FileItem):
        if info is None:
            return ""
        return info.path

    @Template.Callback()
    def get_file_unixtime_modified(self, info: FileItem):
        Gio.FileInfo()
        if info is None:
            return ""
        return info.ctime

    @Template.Callback()
    def get_file_name_factory(self, item: Gtk.ListItem, info: FileItem):
        if info is None:
            return ""
        return info.name

    @Template.Callback()
    def get_file_path_factory(self, item: Gtk.ListItem, info: FileItem):
        if info is None:
            return ""
        return info.path

    @Template.Callback()
    def get_file_time_modified_factory(self, item: Gtk.ListItem, info: FileItem):
        if info is None:
            return 0
        return time.ctime(info.ctime)

    @Template.Callback("on_file_add")
    def show_files_select_dialog(self, *args):
        def on_files_select(dialog: SelectFolderDialog, resp: Gtk.ResponseType):
            if resp != Gtk.ResponseType.APPLY:
                return

            for p in dialog.get_selected_paths():
                self.file_list.append(p)

        return SelectFolderDialog(self, on_files_select)

    def on_drop(self, target: Gtk.DropTarget, value: str, x: float, y: float):
        dialog = self.show_files_select_dialog()
        dialog.folder = value

    @Template.Callback()
    def on_target_clicked(self, *args):
        file_dialog = Gtk.FileDialog()

        def on_file_chosen(src, result):
            try:
                f = file_dialog.select_folder_finish(result)
                self.targetFolder = f.get_path()
            except:
                pass

        file_dialog.select_folder(self, callback=on_file_chosen)

    @Template.Callback()
    def on_execute(self, *args):
        dialog = ProgressDialog(self)
        dialog.set_visible(True)

        target_path = Path(self.targetFolder)

        def copy_files():
            len_files = self.file_sorted_list.get_n_items()
            for i in range(len_files):
                file_item: FileItem = self.file_sorted_list.get_item(i)
                target = target_path / file_item.path

                GLib.idle_add(dialog.set_progress, i / len_files, target)

                if not target.parent.exists():
                    target.parent.mkdir(parents=True, exist_ok=True)
                target.write_bytes(Path(file_item.full_path).read_bytes())

            GLib.idle_add(dialog.set_progress, 1, "")

        thread = threading.Thread(target=copy_files)
        thread.daemon = True
        thread.start()


app = Adw.Application(application_id="io.gitee.zhi.SeqCopy")
app.connect("activate", lambda app: MainWindow(app).present())
app.run()
